package fun.shuofeng.myspringmvc.servlet;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import fun.shuofeng.myspringmvc.annotaion.Autowired;
import fun.shuofeng.myspringmvc.annotaion.Controller;
import fun.shuofeng.myspringmvc.annotaion.RequestMapping;
import fun.shuofeng.myspringmvc.annotaion.Service;
import fun.shuofeng.myspringmvc.utils.MappingInfo;
import fun.shuofeng.myspringmvc.utils.ResponseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * @author 朔风
 * @date 2023-11-08 20:11
 */
@MultipartConfig
public class DispatcherServlet extends HttpServlet {
    private final Logger logger = LoggerFactory.getLogger(DispatcherServlet.class.getName());
    private final Properties properties = new Properties();

    /**
     * 用于存储完整类名，并实例化对象
     */
    private final List<String> classNames = new ArrayList<>();

    /**
     * IOC容器，用于存储实例化之后的对象
     * 通过key-value结构存储，
     * key为类名，value为具体对象
     */
    private final Map<String, Object> IOC = new ConcurrentHashMap<>();

    /**
     * 控制器(Controller)容器，用于存储实例化之后的控制器对象
     * 便于处理映射器调用
     */
    private final Map<String, Object> controllerMap = new ConcurrentHashMap<>();

    /**
     * 处理映射器容器，用于存储请求信息与处理方法的实例对象
     */
    private final Map<String, MappingInfo> handlerMapping = new ConcurrentHashMap<>();


    @Override
    public void init(ServletConfig config) throws ServletException {
        logger.info("init()方法被调用，DispatcherServlet初始化");
        // 1.加载配置文件
        loadConfig(config.getInitParameter("contextConfigLocation"));
        // 2.扫包，得到所有被@Controller和@Service标注的类
        scanPackage(properties.getProperty("package"));
        // 3.将被标注的类实例化并存入ioc容器中
        instanceAndPutIOC();
        // 4.初始化HandlerMapping(将请求url和Method对应上)
        initHandlerMapping();
        // 5.执行依赖注入
        doAutowired();

    }


    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response)
            throws IOException {
        logger.info("service方法被调用");
        try {
            // 接收到请求后做分发调度
            doDispatch(request, response);
        } catch (InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace(System.out);
            // 获得异常详细消息
            String message = e.getCause().getMessage();
            ResponseUtils responseUtils;
            if (message.contains("### Cause:")) {
                responseUtils = new ResponseUtils(message.substring(message.lastIndexOf("### Cause:")), HttpServletResponse.SC_BAD_REQUEST);

            } else {
                responseUtils = new ResponseUtils(message, HttpServletResponse.SC_BAD_REQUEST);
            }
            response.setContentType("application/json;charset=utf-8");
            response.getWriter().write(JSON.toJSONString(responseUtils));
        }
    }

    /**
     * 统一分发调度器，用于接收到请求，将请求转发至对应的处理器映射器中处理
     *
     * @param request  请求对象
     * @param response 响应对象
     */
    private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws IOException, InvocationTargetException, IllegalAccessException {
        if (handlerMapping.isEmpty()) {
            return;
        }
        // 获取本次请求方法
        String requestMethod = request.getMethod();
        // 获取请求的URI路径
        String requestURI = request.getRequestURI();
        // 获取当前web根目录
        String contextPath = request.getContextPath();
        // 截取本次请求的路径
        String path = requestURI.replace(contextPath, "");
        // 如果没有找到对应的请求映射器就返回404并结束处理
        if (!handlerMapping.containsKey(path)) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.getWriter().write("<h1>404 NOT FOUND!</h1>");
            return;
        }
        // 得到请求对应的映射器信息对象
        MappingInfo mappingInfo = handlerMapping.get(path);
        // 从映射器信息实体得到方法对象
        Method method = mappingInfo.getMethod();
        // 从映射器信息实体得到注解对象
        RequestMapping requestMapping = mappingInfo.getRequestMapping();
        // 判断请求方法是否合法
        if (requestMapping.method().equalsIgnoreCase(requestMethod)) {
            // 获取该方法的所有参数信息数组
            Parameter[] parameters = method.getParameters();
            // 定义一个空数组，用来存储实参内容
            Object[] parameterContent = new Object[parameters.length];
            // 循环参数类型列表
            for (int i = 0; i < parameters.length; i++) {
                // 获取该方法的参数类型列表
                Class<?> aClass = parameters[i].getType();
                // 获得参数的完整类型名称
                String simpleName = aClass.getSimpleName();
                // 判断是否为HttpServletRequest类型
                if (simpleName.equals(HttpServletRequest.class.getSimpleName())) {
                    parameterContent[i] = request;
                    continue;
                }
                // 判断是否为HttpServletResponse类型
                if (simpleName.equals(HttpServletResponse.class.getSimpleName())) {
                    parameterContent[i] = response;
                    continue;
                }
                // 判断是否为String类型
                if (simpleName.equals(String.class.getSimpleName())) {
                    // 读取当前请求的输入流对象，并转化为JSON字符串
                    String requestJson = stream2Json(request);
                    parameterContent[i] = requestJson;
                }
            }

            // 将请求的参数以实体类的形式作为实参进行调用，得到方法调用后的结果
            Object result = method.invoke(controllerMap.get(path), parameterContent);
            logger.info("invoke: " + result);
            // 最后确定是否以JSON的形式响应
            if (Objects.nonNull(result)) {
                response.setContentType("application/json;charset=utf-8");
                response.getWriter().write(JSON.toJSONString(result));
            }
        } else {
            response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
            response.getWriter().write("<h1>405 METHOD NOT ALLOWED!</h1>");
        }
    }

    @Override
    public void destroy() {
        logger.info("destroy");
    }


    /**
     * 从web.xml里面的配置项加载对应的配置文件得到扫包路径
     *
     * @param location 配置项参数
     */
    private void loadConfig(String location) {
        if (location.contains("classpath:")) {
            location = location.replace("classpath:", "");
        }
        // 将web.xml中配置文件读取到输入流中
        InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(location);
        try {
            // 解析配置文件到properties中
            properties.load(inputStream);
            logger.info("成功读取配置文件：" + location);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 通过要扫描的包路径，获取包中的所有字节码文件，并以完整类名存储
     *
     * @param packageName 扫描的包路径
     */
    private void scanPackage(String packageName) {
        // 通过类路径得到url
        URL url = this.getClass().getClassLoader().getResource("/" + packageName.replaceAll("\\.", "/"));
        // 通过url得到该文件对象
        File dir = new File(url.getFile());
        for (File file : dir.listFiles()) {
            // 如果该文件是目录
            if (file.isDirectory()) {
                // 递归扫描包
                scanPackage(packageName + "." + file.getName());
            } else {
                // 如果该文件是单纯的文件
                // 那就获取该文件名和包名拼接得到完整类名
                String className = packageName + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }

    }

    /**
     * 实例化对象，并将对象存入IOC容器中
     */
    private void instanceAndPutIOC() {
        // 如果没有存储任何类名就跳过注入
        for (String className : classNames) {
            Class<?> clazz = null;
            try {
                // 通过反射Class.forName得到类对象
                clazz = Class.forName(className);
                // 如果被@Controller注解标注
                if (clazz.isAnnotationPresent(Controller.class)) {
                    // 获取该注解
                    Controller controller = clazz.getDeclaredAnnotation(Controller.class);
                    // 得到该注解的属性值
                    String value = controller.value();
                    //
                    if ("".equals(value)) {
                        // 实例化该对象，以完整类名为key，以对象为value存入IOC容器
                        IOC.put(className, clazz.newInstance());
                    } else {
                        // 如果有设置注解名，则按注解名为key存入
                        IOC.put(value, clazz.newInstance());
                    }
                } else if (clazz.isAnnotationPresent(Service.class)) {
                    // 得到该注解
                    Service service = clazz.getDeclaredAnnotation(Service.class);
                    // 得到该注解的属性值
                    String value = service.value();
                    if ("".equals(value)) {
                        IOC.put(className, clazz.newInstance());
                    } else {
                        IOC.put(value, clazz.newInstance());
                    }
                    Object o = clazz.newInstance();
                    // 如果该类存在父接口则一样存入IOC容器中
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> i : interfaces) {
                        IOC.put(i.getName(), o);
                    }
                }
            } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                throw new RuntimeException(e);
            }
        }


    }

    /**
     * 根据@Autowired自动注入bean对象，仅注入控制器类中的字段
     */
    private void doAutowired() {
        // 如果控制器容器为空，直接返回
        if (controllerMap.isEmpty()) {
            return;
        }

        for (Map.Entry<String, Object> entry : controllerMap.entrySet()) {
            // 得到具体的对象
            Object o = entry.getValue();
            // 得到对象的字节码对象
            Class<?> clazz = o.getClass();
            // 遍历该字节码中的所有字段
            for (Field field : clazz.getDeclaredFields()) {
                // 如果该字段没有被@Autowired标注则跳出本次循环
                if (!field.isAnnotationPresent(Autowired.class)) {
                    continue;
                }
                // 得到该注解的属性值，用于标注要注入的bean名称
                Autowired autowired = field.getDeclaredAnnotation(Autowired.class);
                String beanName = autowired.value();
                // 如果注入的bean名称未设置
                if ("".equals(beanName)) {
                    // 将字段变量完整类型名称作为bean的名称
                    beanName = field.getType().getName();
                }
                // 设置字段可访问
                field.setAccessible(true);
                try {
                    // 根据bean名称从IOC容器获取bean实例，为字段进行依赖注入
                    field.set(o, IOC.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 初始化处理映射器，目的是将请求信息和处理方法的实例对象做映射
     */
    private void initHandlerMapping() {
        // 如果IOC容器为空则直接返回
        if (IOC.isEmpty()) {
            return;
        }
        // 遍历IOC容器
        try {
            for (Map.Entry<String, Object> entry : IOC.entrySet()) {
                // 得到IOC容器中实体对象
                Object o = entry.getValue();
                // 通过对象本身得到字节码对象
                Class<?> clazz = o.getClass();
                // 如果当前字节码没有被@Controller标注直接退出当前循环
                if (!clazz.isAnnotationPresent(Controller.class)) {
                    continue;
                }
                String baseUrl = "";
                // 检测当前字节码有没有被@RequestMapping标注
                if (clazz.isAnnotationPresent(RequestMapping.class)) {
                    RequestMapping requestMapping = clazz.getDeclaredAnnotation(RequestMapping.class);
                    baseUrl = requestMapping.value();
                }
                Method[] methods = clazz.getDeclaredMethods();
                for (Method method : methods) {
                    // 如果没有被@RequestMapping标注就跳过当前循环
                    if (!method.isAnnotationPresent(RequestMapping.class)) {
                        continue;
                    }
                    // 从注解得到请求路径
                    RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
                    String path = requestMapping.value();
                    // 如果有前缀拼接
                    if (path.charAt(0) == '/') {
                        path = baseUrl + path;
                    } else {
                        path = baseUrl + "/" + path;
                    }
                    // 封转请求映射器
                    MappingInfo requestInfo = new MappingInfo(requestMapping, method);
                    // 将请求路径作为key，请求信息作为value
                    handlerMapping.put(path, requestInfo);
                    // 将请求路径作为key，控制器对象作为value，方便处理对应请求
                    controllerMap.put(path, clazz.newInstance());
                    logger.info(path + ":" + method);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 将流转化为JSON字符串
     *
     * @param request 请求对象
     * @return 返回JSON字符串
     */
    private String stream2Json(HttpServletRequest request) throws IOException {
        int contentLength = request.getContentLength();
        if (contentLength < 0) {
            return null;
        }
        byte[] buffer = new byte[contentLength];
        for (int i = 0; i < contentLength; ) {
            int result = request.getInputStream().read(buffer, i, contentLength - i);
            if (result == -1) {
                break;
            }
            i += result;
        }
        return new String(buffer);
    }


}
